package com.geely.club.util.openapiclient;

import android.util.Log;

import com.geely.club.util.httpclientpatch.RequestParams;
import com.geely.club.util.httpclientpatch.SyncHttpUtil;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import cn.evun.gap.openapi.client.binding.RequestMethod;
import cn.evun.gap.openapi.client.binding.RopClientException;
import cn.evun.gap.openapi.client.binding.ServiceMethodDefinition;
import cn.evun.gap.openapi.client.binding.ServiceParam;
import cn.evun.gap.openapi.client.model.common.FileModelDTO;
import cn.evun.gap.openapi.client.model.openapi.ServiceRequestDTO;
import cn.evun.gap.openapi.client.model.openapi.ServiceResponseDTO;
import cn.evun.gap.openapi.client.serialize.JsonUtils;
import cn.evun.gap.openapi.client.serialize.support.json.MyTypeReference2;
import cn.evun.gap.openapi.client.utils.DateUtils;
import cn.evun.gap.openapi.client.utils.EncodeUtils;
import cn.evun.gap.openapi.client.utils.StringUtils;

/**
 * Created by mengyangyang on 2015-10-15.
 */
public class OpenApiClient extends cn.evun.gap.openapi.client.core.OpenApiClient {

    public static final String ENCODING_UTF_8 = "UTF-8";

    private static final String FORMAT_JSON = "json";

    private static final String SERVICE_REQUEST_NAME = "__serviceRequest";

    private static final String FILE_MIME_TYPE = "application/octet-stream";

    private static final String PARAMETER_START = "?";

    public static final String PARAMATER_ASSIGN = "=";

    public static final String PARAMATER_ARGS_MORE = "&";

    private String encode = ENCODING_UTF_8;

    private String appkey;

    private String serviceUrl;

    private String secret;

    public String getSecret() {
        return secret;
    }

    public void setSecret(String secret) {
        this.secret = secret;
    }

    public String getEncode() {
        return encode;
    }

    public void setEncode(String encode) {
        this.encode = encode;
    }

    public void setServiceUrl(String serviceUrl) {
        this.serviceUrl = serviceUrl;
    }

    public String getAppkey() {
        return appkey;
    }

    public void setAppkey(String appkey) {
        this.appkey = appkey;
    }

    private HttpResponse resolvePostMethod(Method method, ServiceRequestDTO serviceRequest, Object[] args) throws IOException {
        List<FileModelDTO> fileArgs = getFileArgs(method, args);
        HttpResponse response = null;
        ProgressCallBack progressCallBack = null;

        List<NameValuePair> params = getRequestParams(method, serviceRequest, args);
        RequestParams requestParams = getRequestParams(params);
        if (!fileArgs.isEmpty()) {
            for (Object obj : args) {
                if (obj instanceof ProgressCallBack) {
                    progressCallBack = (ProgressCallBack) obj;
                    break;
                }
            }
            for (FileModelDTO fileModel : fileArgs) {
                if (fileModel.isInputStreamFile()) {
                    requestParams.put(fileModel.getArgName(), fileModel.getInputStream(), fileModel.getFileName());
                } else if (fileModel.isByteFile()) {
                    requestParams.put(fileModel.getArgName(), fileModel.getBytes());
                } else {
                    requestParams.put(fileModel.getArgName(), fileModel.getFile(), FILE_MIME_TYPE, fileModel.getFileName());
                }
            }
        }
        return SyncHttpUtil.post(serviceUrl, requestParams, progressCallBack);
    }

    private String getArgName(Method method, int index, Object arg) {

        String attrName = null;

        int annotationsFound = 0;

        Annotation[] paramAnns = method.getParameterAnnotations()[index];

        for (Annotation paramAnn : paramAnns) {
            if (ServiceParam.class.isInstance(paramAnn)) {
                ServiceParam modelAttribute = (ServiceParam) paramAnn;
                attrName = modelAttribute.value();
                annotationsFound++;
            }
        }

        if (annotationsFound > 1) {
            throw new RopClientException("Handler parameter annotations are exclusive choices - " + "do not specify more than one such annotation on the same parameter: " + method.getName());
        }

        if (StringUtils.isEmpty(attrName) && !isWrapClass(arg)) {
            attrName = StringUtils.uncapitalize(arg.getClass().getSimpleName());
        }

        if (StringUtils.isEmpty(attrName)) {
            return "";
//            throw new RopClientException("handler parameter must supply annotation ServiceParam!");
        }

        return attrName;
    }

    private List<NameValuePair> getRequestParams(Method method, ServiceRequestDTO serviceRequest, Object[] args) {
        List<NameValuePair> params = new ArrayList<NameValuePair>();

        for (int i = 0; i < args.length - 1; i++) {
            Object arg = args[i];
            if (null == arg || File.class.isAssignableFrom(arg.getClass()) || FileModelDTO.class.isAssignableFrom(arg.getClass()) || arg instanceof ProgressCallBack) {
                continue;
            }

            if (isWrapClass(arg) || Date.class.isAssignableFrom(arg.getClass())) {
                if (Date.class.isAssignableFrom(arg.getClass())) {
                    params.add(new BasicNameValuePair(getArgName(method, i, arg), DateUtils.formatTime((Date) arg)));
                } else {
                    params.add(new BasicNameValuePair(getArgName(method, i, arg), String.valueOf(arg)));
                }
            } else {
                params.add(new BasicNameValuePair(getArgName(method, i, arg), JsonUtils.toJSON(arg)));
            }
        }

        serviceRequest.setSign(EncodeUtils.sign(params, serviceRequest, secret));
        params.add(new BasicNameValuePair(SERVICE_REQUEST_NAME, JsonUtils.toJSON(serviceRequest)));

        return params;
    }

    @SuppressWarnings("unchecked")
    private List<FileModelDTO> getFileArgs(Method method, Object[] args) {
        if (null == args) {
            return Collections.emptyList();
        }

        final List<FileModelDTO> fileArgs = new LinkedList<FileModelDTO>();

        for (int i = 0; i < args.length - 1; i++) {
            Object arg = args[i];
            if (null == arg) {
                continue;
            }
            Class<?> paramType = arg.getClass();
            FileModelDTO fileModel = null;
            //如果是集合类型List<File>,List<FileModelDTO>另当处理
            if (List.class.isAssignableFrom(paramType)) {
                ParameterizedType pType = (ParameterizedType) method.getGenericParameterTypes()[i];
                Class<?> childType = (Class<?>) pType.getActualTypeArguments()[0];
                final String argName = getArgName(method, i, arg);
                if (File.class.isAssignableFrom(childType)) {
                    final List<File> files = (List<File>) arg;
                    for (File file : files) {
                        FileModelDTO fModel = new FileModelDTO(file);
                        addFileModel(fileArgs, argName, fModel);
                    }
                } else if (FileModelDTO.class.isAssignableFrom(childType)) {
                    final List<FileModelDTO> files = (List<FileModelDTO>) arg;
                    for (FileModelDTO fModel : files) {
                        addFileModel(fileArgs, argName, fModel);
                    }
                }
            }
            if (File.class.isAssignableFrom(paramType)) {
                fileModel = new FileModelDTO((File) arg);
            } else if (FileModelDTO.class.isAssignableFrom(paramType)) {
                fileModel = (FileModelDTO) arg;
            }
            addFileModel(fileArgs, getArgName(method, i, arg), fileModel);
        }

        return fileArgs;
    }

    private void addFileModel(List<FileModelDTO> fileArgs, String argName, FileModelDTO fileModel) {
        if (null != fileModel) {
            fileModel.setArgName(argName);
            fileArgs.add(fileModel);
        }
    }

    private HttpResponse resolveGetMethod(Method method, ServiceRequestDTO serviceRequest, Object[] args) throws IOException {
        if (!getFileArgs(method, args).isEmpty()) {
            throw new RopClientException("service method <<" + serviceRequest.getMethod() + ">> send module.cloud.file must be post action!");
        }
        List<NameValuePair> params = getRequestParams(method, serviceRequest, args);
        RequestParams requestParams = getRequestParams(params);
        return SyncHttpUtil.get(serviceUrl, requestParams);
    }

    private RequestParams getRequestParams(List<NameValuePair> params) {
        RequestParams requestParams = new RequestParams();
        if (null != params) {
            for (NameValuePair nameValuePair : params) {
                requestParams.add(nameValuePair.getName(), nameValuePair.getValue());
            }
        }
        return requestParams;
    }

    public Object service(Method method, ServiceMethodDefinition serviceMethodDefinition, Object[] args) {

        final ServiceRequestDTO serviceRequest = createServieRequest(serviceMethodDefinition);

        HttpResponse response = null;
        long time = System.currentTimeMillis();
        try {
            if (RequestMethod.GET.equals(serviceMethodDefinition.getAction())) {
                response = resolveGetMethod(method, serviceRequest, args);
            } else {
                response = resolvePostMethod(method, serviceRequest, args);
            }
        } catch (IOException e) {
            throw new RopClientException("execute remote service function catch exception!" + e.getMessage(), e);
        }
        Log.i("HttpRequestTimeConsume", "" + (System.currentTimeMillis() - time) / 1000f);

        int statusCode = response.getStatusLine().getStatusCode();

        if (HttpURLConnection.HTTP_OK != statusCode) {
            throw new RopClientException("Method failed:" + response.getStatusLine());
        }

        String responseBody = null;

        try {
            responseBody = EntityUtils.toString(response.getEntity(), ENCODING_UTF_8);
        } catch (IOException e) {
            throw new RopClientException("format response entity failed:" + e.getMessage(), e);
        }

        ServiceResponseDTO serviceResponse = JsonUtils.fromJSON(responseBody, ServiceResponseDTO.class);

        if (null == serviceResponse) {
            throw new RopClientException("service framework return error message:" + responseBody);
        }

        if (!serviceResponse.isSuccess()) {
            throw new RopClientException(serviceResponse.getErrorMessage(), serviceResponse.getCode());
        }

        if (null != serviceResponse.getData() && !method.getReturnType().isAssignableFrom(serviceResponse.getData().getClass())) {
            Object data = JsonUtils.fromJSON(JsonUtils.toJSON(serviceResponse.getData()), method.getReturnType());
            if (null != data) {
                serviceResponse.setData(data);
            }
        }

        if (null != serviceResponse.getData() && (List.class.isAssignableFrom(method.getReturnType()) || Map.class.isAssignableFrom(method.getReturnType()))) {
            Object data = JsonUtils.fromJSON(JsonUtils.toJSON(serviceResponse.getData()), new MyTypeReference2<Object>(method.getGenericReturnType()));
            if (null != data) {
                serviceResponse.setData(data);
            }
        }

        return serviceResponse.getData();
    }

    public static boolean isWrapClass(Object obj) {
        Class<?> type = obj.getClass();
        try {
            return (String.class.isAssignableFrom(type)) || ((Class<?>) type.getField("TYPE").get(null)).isPrimitive();
        } catch (Exception e) {
            return false;
        }
    }


    private ServiceRequestDTO createServieRequest(ServiceMethodDefinition serviceMethodDefinition) {
        final ServiceRequestDTO serviceRequest = new ServiceRequestDTO();
        serviceRequest.setAppkey(this.appkey);
        serviceRequest.setTicket(serviceMethodDefinition.getTicket());
        serviceRequest.setV(serviceMethodDefinition.getVersion());
        serviceRequest.setMethod(serviceMethodDefinition.getMethod());
        serviceRequest.setTimestamp(new Date().getTime());
        serviceRequest.setFormat(FORMAT_JSON);
        return serviceRequest;
    }


}
